package com.yky.utils;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.jedis.JedisClusterConnection;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 业务聚合
 *
 * @version [V2.0]
 * @package com.example.utils;
 * @description:[用一句话描述该文件做什么]
 * @author: [zhuben]
 * @date: [2024-02-02 15:34]
 * @copyright: 2024 echovin.com
 */

@Component
@Slf4j
public final class RedisUtils {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    //=========================通用==========================
    /**
     * 指定缓存失效时间
     * @param key  键
     * @param time 时间(秒)
     */
    public boolean expire(final String key, final long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(final String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }


    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(final String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除缓存
     * @param keys 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(final String... keys) {
        if (keys != null && keys.length > 0) {
            if (keys.length == 1) {
                redisTemplate.delete(keys[0]);
            } else {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(keys));
            }
        }
    }

    /**
     * 获取所有key
     * @return
     */
    public Set<String> scan(String pattern){
        Set<String> keys = new HashSet();
        RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        RedisConnection redisConnection = connectionFactory.getConnection();
        Cursor<byte[]> scan = null;
        if(redisConnection instanceof JedisClusterConnection){
            RedisClusterConnection clusterConnection = connectionFactory.getClusterConnection();
            Iterable<RedisClusterNode> redisClusterNodes = clusterConnection.clusterGetNodes();
            Iterator<RedisClusterNode> iterator = redisClusterNodes.iterator();
            while (iterator.hasNext()) {
                RedisClusterNode next = iterator.next();
                scan = clusterConnection.scan(next, ScanOptions.scanOptions().match(pattern).count(Integer.MAX_VALUE).build());
                while (scan.hasNext()) {
                    keys.add(new String(scan.next()));
                }
                try {
                    scan.close();
                } catch (Exception e) {
                    log.error("scan遍历key关闭游标异常", e);
                }
            }
            return keys;
        }
        if(redisConnection instanceof JedisConnection){
            scan = redisConnection.scan(ScanOptions.scanOptions().match(pattern).count(Integer.MAX_VALUE).build());
            while (scan.hasNext()){
                //找到一次就添加一次
                keys.add(new String(scan.next()));
            }
            try {
                scan.close();
            } catch (Exception e) {
                log.error("scan遍历key关闭游标异常", e);
            }
            return keys;
        }

        return keys;
    }

    /**
     * 分页查询
     * @param key
     * @param page
     * @param size
     * @return
     * @throws Exception
     */
    public Page ofPage(String key, int page, int size) throws Exception {
        DataType type = redisTemplate.type(key);
        Page pageMode = new Page(0, new Object());
        if (type == null){
            return pageMode;
        }
        int start = (page-1)*size;
        int end = start + (size-1);

        switch (type){
            case STRING:
                pageMode =  new Page(1, this.get(key));
                break;
            case LIST:
                pageMode = new Page(this.lSize(key), this.lRange(key, start, end));
                break;
            case SET:
                pageMode = new Page(this.sSize(key), this.sMembers(key));
                break;
            case HASH:
                pageMode = new Page(this.hSize(key), this.hPage(key, page, size));
                break;
            case ZSET:
                pageMode = new Page(this.zCard(key), this.zRevRangeWithScore(key, start, end));
                break;
            default:
                throw new Exception("key:"+key+",类型不存在");
        }
        return pageMode;
    }

// ================================String=================================
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public <T> T get(final String key) {
        if (key == null){
            return null;
        }
        ValueOperations<String, T> operations = (ValueOperations<String, T>) redisTemplate.opsForValue();
        return operations.get(key);
    }

    /**
     * 普通缓存放入
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */

    public <T> boolean set(final String key, final T value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */

    public <T> boolean set(final String key, final T value, final long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 适合分布式锁
     * @param key
     * @param value
     * @return
     */
    public <T> boolean setNx(final String key, final T value){
        try {
            return redisTemplate.opsForValue().setIfAbsent(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 适合分布式锁
     * @param key
     * @param value
     * @param time
     * @return
     */
    public <T> boolean setEx(final String key, final T value, final long time) {
        try {
            if (time > 0) {
                return redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
            } else {
                return setNx(key, value);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     * @param key   键
     * @param delta 要增加几(大于0)
     */
    public long incr(final String key, final long delta) {
        if (delta <= 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     * @param key   键
     * @param delta 要减少几(小于0)
     */
    public long decr(final String key, final long delta) {
        if (delta <= 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    // ================================Map=================================

    /**
     * HashGet
     * @param key  键 不能为null
     * @param item 项 不能为null
     */
    public <T> T hget(final String key, final Object item) {
        HashOperations<String, String, T> operations = redisTemplate.opsForHash();
        return operations.get(key, item);
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public <T> boolean hset(final String key, final String item, final T value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public <T> boolean hset(final String key, final String item, final T value, final long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public <T> Map<String, T> hmget(final String key) {
        HashOperations<String, String, T> operations = redisTemplate.opsForHash();
        return operations.entries(key);
    }

    /**
     * 获取
     * @param key
     * @return
     */
    public long hSize(final String key){
        return redisTemplate.opsForHash().size(key);
    }
    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     */
    public <T> boolean hmset(final String key, final Map<String, T> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public <T> boolean hmset(final String key, final Map<String, T> map, final long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(final String key, final Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(final String key, final String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     */
    public double hincr(final String key, final String item, final double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     */
    public double hdecr(final String key, final String item, final double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    /**
     * 获取哈希表中的所有字段
     * @param key
     * @return
     */
    public Set<Object> hKeys(final String key){
        return redisTemplate.opsForHash().keys(key);
    }

    /**
     * hash分页显示
     * @param key
     * @param page
     * @param size
     * @return
     */
    public List<Map<Object, Object>> hPage(final String key, final int page, final int size){
        long count = this.hSize(key);
        int start = (page-1)*size;
        int end = start+size;
        List<Map<Object, Object>> result = new ArrayList<>();
        if (count > 0 && start<count){
            Set<Object> objects = this.hKeys(key);
            if (end > count){
                end = (int)count;
            }
            List<Object> list = new ArrayList<>(objects);
            List<Object> objectList = list.subList(start, end);
            for (Object o : objectList){
                Map<Object, Object> data = new HashMap<>();
                data.put(o, this.hget(key, o));
                result.add(data);
            }

        }
        return result;
    }
    // ============================set=============================
    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public <T> long sAdd(final String key, final T... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            return count == null ? 0 : count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public <T> long sAddAndTime(final String key, final long time, final T... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) {
                expire(key, time);
            }
            return count == null ? 0 : count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 根据key获取Set中的所有值
     * @param key 键
     */
    public <T> Set<T> sMembers(final String key) {
        try {
            SetOperations<String, T> operations = (SetOperations<String, T>) redisTemplate.opsForSet();
            return operations.members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public <T> boolean sIsMember(final String key, final T value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 获取set缓存的长度
     *
     * @param key 键
     */
    public long sSize(final String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */

    public <T> long sRemove(final String key, final T... values) {
        try {
            return redisTemplate.opsForSet().remove(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 返回给定集合的并集。不存在的集合 key 被视为空集
     * @param key1
     * @param key2
     * @return
     */
    public <T> Set<T> sUnion(final String key1, final String key2){
        SetOperations<String, T> operations = (SetOperations<String, T>) redisTemplate.opsForSet();
        return operations.union(key1, key2);
    }
    // ===============================list=================================

    /**
     * 将一个值插入到列表头部
     * @param key 键
     * @param value 值
     */
    public <T> boolean leftPush(final String key, final T value) {
        try {
            redisTemplate.opsForList().leftPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将多个值插入到列表头部
     *
     * @param key   键
     * @param values 值
     */
    public <T> boolean leftPushAll(final String key, final T... values) {
        try {
            redisTemplate.opsForList().leftPushAll(key, values);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移出并获取列表的第一个元素
     * @param key
     * @return
     */
    public <T> T leftPop(final String key){
        try {
            return (T) redisTemplate.opsForList().leftPop(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 在列表中添加一个或多个值到列表尾部
     * @param key 键
     * @param value 值
     * @param <T> 插入成功个数
     * @return
     */
    public <T> boolean rightPush(final String key, final T value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public <T> boolean rightPushAll(final String key, final List<T> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除列表的最后一个元素，返回值为移除的元素。
     * @param key
     * @return
     */
    public <T> T rightPop(final String key){
        try {
            return (T) redisTemplate.opsForList().rightPop(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     */
    public <T> List<T> lRange(final String key, final long start, final long end) {
        try {
            return (List<T>) redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     *
     * @param key 键
     */
    public long lSize(final String key) {
        try {
            Long size = redisTemplate.opsForList().size(key);
            return size == null ? 0L : size;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return
     */
    public <T> boolean lSet(final String key, final long index, final T value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     * count > 0 : 从表头开始向表尾搜索，移除与 VALUE 相等的元素，数量为 COUNT 。
     * count < 0 : 从表尾开始向表头搜索，移除与 VALUE 相等的元素，数量为 COUNT 的绝对值。
     * count = 0 : 移除表中所有与 VALUE 相等的值
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public <T> long lRemove(final String key, final long count, final T value) {
        try {
            return redisTemplate.opsForList().remove(key, count, value);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    //===========================ZSET===============================

    /**
     * 向有序集合添加一个或多个成员，或者更新已存在成员的分数
     * @param key
     * @param value
     * @param score
     * @return
     */
    public boolean zAdd(final String key, final String value, final double score){
        try {
            return redisTemplate.opsForZSet().add(key, value, score);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 获取有序集合的成员数
     * @param key
     * @return
     */
    public long zCard(final String key){
        try {
            return redisTemplate.opsForZSet().zCard(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 计算在有序集合中指定区间分数的成员数
     * @param key
     * @param min
     * @param max
     * @return
     */
    public long zCount(final String key, final double min, final double max){
        try {
            return redisTemplate.opsForZSet().count(key, min, max);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 有序集合中对指定成员的分数加上增量 increment
     * @param key
     * @param value
     * @param score
     * @return 增加后的值
     */
    public double zIncrby(final String key, final String value, final double score) {
        return redisTemplate.opsForZSet().incrementScore(key, value, score);
    }

    /**
     * 通过索引区间返回有序集合指定区间内的成员
     * @param key
     * @param start
     * @param end
     * @return
     */
    public <T> Set<T> zRange(final String key, final long start, final long end){
        try {
            return (Set<T>) redisTemplate.opsForZSet().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return new HashSet<>();
        }
    }

    /**
     * 通过索引区间返回有序集合指定区间内的成员 倒序（分数从大到小）
     * @param key
     * @param start
     * @param end
     * @param <T>
     * @return
     */
    public <T> Set<T> zReverseRange(final String key, final long start, final long end){
        try {
            return (Set<T>) redisTemplate.opsForZSet().reverseRange(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return new HashSet<>();
        }
    }

    /**
     * 返回有序集中成员的排名。其中有序集成员按分数值递减(从小到大)排序
     * @param key
     * @param value
     * @return
     */
    public long zRank(final String key, final Object value){
        try {
            Long aLong = redisTemplate.opsForZSet().rank(key, value);
            return aLong == null ? -1 : aLong;
        } catch (Exception e) {
            e.printStackTrace();
            return -1;
        }
    }

    /**
     * 返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序
     * @param key
     * @param value
     * @return
     */
    public long zReverseRank(final String key, final String value){
        try {
            Long aLong = redisTemplate.opsForZSet().reverseRank(key, value);
            return aLong == null ? -1 : aLong;
        } catch (Exception e) {
            e.printStackTrace();
            return -1;
        }
    }

    /**
     * 返回有序集中，成员的分数值
     * @param key
     * @param value
     * @return
     */
    public double zScore(final String key, final String value){
        try {
            return redisTemplate.opsForZSet().score(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 返回有序集合成员的排名及分数，分数由大到小
     * @param key
     * @param start
     * @param end
     * @return
     */
    public <T> List<T> zRevRangeWithScore(final String key, final long start, final long end){
        List<T> result = new ArrayList<>();
        try {
            ZSetOperations<String, T> operations = (ZSetOperations<String, T>) redisTemplate.opsForZSet();
            Set<ZSetOperations.TypedTuple<T>> typedTuples = operations.reverseRangeWithScores(key, start, end);
            if (typedTuples == null || typedTuples.size() <= 0){
                return new ArrayList<>();
            }
            for (ZSetOperations.TypedTuple<T> d : typedTuples){
                result.add(d.getValue());
            }
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return result;
        }
    }


    public class Page{
        private long total;
        private Object data;

        public Page(long total, Object data) {
            this.total = total;
            this.data = data;
        }

        public long getTotal() {
            return total;
        }

        public void setTotal(long total) {
            this.total = total;
        }

        public Object getData() {
            return data;
        }

        public void setData(Object data) {
            this.data = data;
        }
    }
}
